/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
   Copyright (C) 2010 Red Hat, Inc.
   Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
   Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.0 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/
#include "config.h"

#include "gio-coroutine.h"

typedef struct _GConditionWaitSource
{
    GSource src;
    GConditionWaitFunc func;
    gpointer data;
} GConditionWaitSource;


/* Main loop helper functions */
static gboolean g_io_wait_helper(GSocket *sock G_GNUC_UNUSED,
				 GIOCondition cond,
				 gpointer data)
{
    struct lock_and_cond* lac = data;
    g_mutex_lock(lac->socket_lock);
	lac->socket_notified = TRUE;
	//SPICE_DEBUG("g_io_wait_helper, signal, lac = %#x\n", lac);
    g_cond_signal(lac->socket_cond);
    g_mutex_unlock(lac->socket_lock);
    return FALSE;
}

void g_coroutine_socket_wait(GSocket *sock, GIOCondition cond)
{
    GSource *src;
    struct lock_and_cond* lac = coroutine_get_lac(g_thread_self());

    g_return_if_fail(lac != NULL);
    g_return_if_fail(lac->wait_id == 0);
    g_return_if_fail(sock != NULL);

    // to ensure this thread is waiting before signal
    g_mutex_lock(lac->socket_lock);
    if (!lac->pending_wakeup) {
		lac->socket_notified = FALSE;
		src = g_socket_create_source(sock, cond | G_IO_HUP | G_IO_ERR | G_IO_NVAL, NULL);
		g_source_set_callback(src, (GSourceFunc)g_io_wait_helper, lac, NULL);
		lac->wait_id = g_source_attach(src, NULL);
		while (!lac->socket_notified)
			g_cond_wait(lac->socket_cond, lac->socket_lock);
		g_source_unref(src);
    }
	lac->pending_wakeup = FALSE;
    lac->wait_id = 0;
    g_mutex_unlock(lac->socket_lock);
}

void g_coroutine_condition_cancel(GThread* thread)
{
    g_return_if_fail(thread != NULL);
    struct lock_and_cond* lac = coroutine_get_lac(thread);

	g_mutex_lock(lac->condition_lock);
    if (lac->condition_id) {
        g_source_remove(lac->condition_id);
    }
    lac->condition_id = 0;
    lac->condition_notified = TRUE;
    //SPICE_DEBUG("g_coroutine_condition_cancel, signal, lac = %#x\n", lac);
    g_cond_signal(lac->condition_cond);
    g_mutex_unlock(lac->condition_lock);
}

void g_coroutine_wakeup(GThread* thread)
{
    g_return_if_fail(thread != NULL);

    struct lock_and_cond* lac = coroutine_get_lac(thread);
    g_mutex_lock(lac->socket_lock);
    if (lac->wait_id) {
        g_source_remove(lac->wait_id);
    }
	lac->socket_notified = TRUE;
	//SPICE_DEBUG("g_coroutine_wakeup, signal, lac = %#x\n", lac);
    g_cond_signal(lac->socket_cond);
    lac->pending_wakeup = TRUE;
    g_mutex_unlock(lac->socket_lock);
}

/*
 * Call immediately before the main loop does an iteration. Returns
 * true if the condition we're checking is ready for dispatch
 */
static gboolean g_condition_wait_prepare(GSource *src,
					 int *timeout) {
    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
    *timeout = -1;
    return vsrc->func(vsrc->data);
}

/*
 * Call immediately after the main loop does an iteration. Returns
 * true if the condition we're checking is ready for dispatch
 */
static gboolean g_condition_wait_check(GSource *src)
{
    GConditionWaitSource *vsrc = (GConditionWaitSource *)src;
    return vsrc->func(vsrc->data);
}

static gboolean g_condition_wait_dispatch(GSource *src G_GNUC_UNUSED,
					  GSourceFunc cb,
					  gpointer data) {
    return cb(data);
}

GSourceFuncs waitFuncs = {
    .prepare = g_condition_wait_prepare,
    .check = g_condition_wait_check,
    .dispatch = g_condition_wait_dispatch,
};

static gboolean g_condition_wait_helper(gpointer data)
{
    struct lock_and_cond* lac = data;
    g_mutex_lock(lac->condition_lock);
    lac->condition_notified = TRUE;
    //SPICE_DEBUG("g_condition_wait_helper, signal, lac = %#x\n", lac);
    g_cond_signal(lac->condition_cond);
    g_mutex_unlock(lac->condition_lock);
    return FALSE;
}

/*
 * g_coroutine_condition_wait:
 * @coroutine: the coroutine to wait on
 * @func: the condition callback
 * @data: the user data passed to @func callback
 *
 * This function will wait on caller coroutine until @func returns %TRUE.
 *
 * @func is called when entering the main loop from the main context (coroutine).
 *
 * The condition can be cancelled by calling g_coroutine_wakeup()
 *
 * Returns: %TRUE if condition reached, %FALSE if not and cancelled
 */
gboolean g_coroutine_condition_wait(GConditionWaitFunc func, gpointer data)
{
    GSource *src;
    GConditionWaitSource *vsrc;
    struct lock_and_cond* lac = coroutine_get_lac(g_thread_self());

    g_return_val_if_fail(lac != NULL, FALSE);
    g_return_val_if_fail(lac->condition_id == 0, FALSE);
    g_return_val_if_fail(func != NULL, FALSE);

    /* Short-circuit check in case we've got it ahead of time */
    if (func(data))
        return TRUE;

    /*
     * Don't have it, so yield to the main loop, checking the condition
     * on each iteration of the main loop
     */
    src = g_source_new(&waitFuncs, sizeof(GConditionWaitSource));
    vsrc = (GConditionWaitSource *)src;

    vsrc->func = func;
    vsrc->data = data;

    // to ensure this thread is waiting before signal
    g_mutex_lock(lac->condition_lock);
    lac->condition_notified = FALSE;
    lac->condition_id = g_source_attach(src, NULL);
    g_source_set_callback(src, g_condition_wait_helper, lac, NULL);
    //SPICE_DEBUG("g_coroutine_condition_wait, waiting, lac = %#x, thread = %#x\n", lac, g_thread_self());
    while (!lac->condition_notified)
        g_cond_wait(lac->condition_cond, lac->condition_lock);

    g_source_unref(src);

    /* it got woked up / cancelled? */
    if (lac->condition_id == 0)
        return func(data);

    lac->condition_id = 0;
    g_mutex_unlock(lac->condition_lock);
    return TRUE;
}

struct signal_data
{
    gpointer instance;
    guint signal_id;
    GQuark detail;
    const gchar *propname;
    va_list var_args;
	struct lock_and_cond* lac;
};

static gboolean emit_main_context(gpointer opaque)
{
    struct signal_data *signal = opaque;

    g_signal_emit_valist(signal->instance, signal->signal_id,
                         signal->detail, signal->var_args);

    if (!signal->lac->run_cancelled) {
        g_mutex_lock(signal->lac->run_lock);
        signal->lac->run_notified = TRUE;
        //SPICE_DEBUG("emit_main_context, signal, lac = %#x\n", signal->lac);
        g_cond_signal(signal->lac->run_cond);
        g_mutex_unlock(signal->lac->run_lock);
    }

    g_free(signal);
    return FALSE;
}

void
g_coroutine_signal_emit(gpointer instance, guint signal_id,
                        GQuark detail, ...)
{
    struct signal_data* pdata = g_malloc(sizeof(struct signal_data));
    pdata->instance = instance;
    pdata->signal_id = signal_id;
    pdata->detail = detail;

    va_start (pdata->var_args, detail);
    va_list* pargs = &pdata->var_args;

    if (coroutine_self_is_main()) {
        g_signal_emit_valist(instance, signal_id, detail, pdata->var_args);
        g_free(pdata);
    } else {
        g_object_ref(instance);
        struct lock_and_cond* lac = coroutine_get_lac(g_thread_self());
        lac->run_cancelled = FALSE;
        pdata->lac = lac;
        g_mutex_lock(lac->run_lock);
        lac->run_notified = FALSE;
        g_idle_add(emit_main_context, pdata);
        //SPICE_DEBUG("g_coroutine_signal_emit, waiting, lac = %#x, thread = %#x\n", lac, g_thread_self());
        while (!lac->run_notified)
            g_cond_wait(lac->run_cond, lac->run_lock);
        g_mutex_unlock(lac->run_lock);

        lac->run_cancelled = TRUE;

        g_object_unref(instance);
    }

    va_end (*pargs);
}


static gboolean notify_main_context(gpointer opaque)
{
    struct signal_data *signal = opaque;

    g_object_notify(signal->instance, signal->propname);
    if (!signal->lac->run_cancelled) {
        g_mutex_lock(signal->lac->run_lock);
        signal->lac->run_notified = TRUE;
        //SPICE_DEBUG("notify_main_context, signal, lac = %#x\n", signal->lac);
        g_cond_signal(signal->lac->run_cond);
        g_mutex_unlock(signal->lac->run_lock);
    }
    g_free(signal);
    return FALSE;
}

/* coroutine -> main context */
void g_coroutine_object_notify(GObject *object,
                               const gchar *property_name)
{
    struct signal_data* pdata = g_malloc(sizeof(struct signal_data));

    if (coroutine_self_is_main()) {
        g_object_notify(object, property_name);
        g_free(pdata);
    } else {
        pdata->instance = g_object_ref(object);
        pdata->propname = (gpointer)property_name;
        struct lock_and_cond* lac = coroutine_get_lac(g_thread_self());
        lac->run_cancelled = FALSE;
        pdata->lac = lac;

        // to ensure this thread is waiting before signal
        g_mutex_lock(lac->run_lock);
        lac->run_notified = FALSE;
        g_idle_add(notify_main_context, pdata);

        //SPICE_DEBUG("g_coroutine_object_notify, waiting, lac = %#x, thread = %#x\n", lac, g_thread_self());
        while (!lac->run_notified)
            g_cond_wait(lac->run_cond, lac->run_lock);

        g_mutex_unlock(lac->run_lock);
        lac->run_cancelled = TRUE;
        g_object_unref(object);
    }
}
